From: Colin Walters Date: Thu, 30 Jul 2009 19:40:26 +0000 (-0400) Subject: Bug 588343 - Major rework of window monitoring to be application-based X-Git-Tag: archive/raspbian/50.5-1+rpi1~1^2~1013^2^2~11161 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=2726fdb831345c51a632e72e56c0ac20b03d172e;p=gnome-shell.git Bug 588343 - Major rework of window monitoring to be application-based The previous application monitoring code was originally designed to be based on WM_CLASS, which was then resolved on a server. We have that resolution code locally now, so instead of saving WM_CLASS data, save application IDs. Also, inside the WM we have a much better infrastructure for tracking windows. In particular, rather than polling, we can just watch for focus notification on the display, and window add/remove. Instead of polling XScreensaver, use DBus to watch org.gnome.Session which already has an idle time watch. Now there is no polling at all inside the monitor. --- diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 7025e4e76..9a4c2a7c0 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -277,7 +277,8 @@ AppDisplay.prototype = { }, _getMostUsed: function() { - return this._appMonitor.get_most_used_apps(0, 30).map(Lang.bind(this, function (id) { + let context = ""; + return this._appMonitor.get_most_used_apps(context, 30).map(Lang.bind(this, function (id) { return this._appSystem.lookup_app(id + '.desktop'); })).filter(function (e) { return e != null }); }, @@ -723,6 +724,9 @@ AppWell.prototype = { }, _redisplay: function() { + /* hardcode here pending some design about how exactly activities behave */ + let contextId = ""; + let arrayToObject = function(a) { let o = {}; for (let i = 0; i < a.length; i++) @@ -731,7 +735,8 @@ AppWell.prototype = { }; let favoriteIds = this._appSystem.get_favorites(); let favoriteIdsObject = arrayToObject(favoriteIds); - let runningIds = this._appMonitor.get_running_app_ids().filter(function (e) { + + let runningIds = this._appMonitor.get_running_app_ids(contextId).filter(function (e) { return !(e in favoriteIdsObject); }); let favorites = this._lookupApps(favoriteIds); diff --git a/src/shell-app-monitor.c b/src/shell-app-monitor.c index 379f1b40d..57107047e 100644 --- a/src/shell-app-monitor.c +++ b/src/shell-app-monitor.c @@ -10,7 +10,7 @@ #include #include #include - +#include #include "shell-app-monitor.h" #include "shell-app-system.h" @@ -26,20 +26,39 @@ * Copyright Red Hat, Inc. 2006-2008 */ +/** + * SECTION:shell-app-monitor + * @short_description: Associate windows with application data and track usage/state data + * + * The application monitor has two primary purposes. First, it + * maintains a mapping from windows to applications (.desktop file ids). + * It currently implements this with some heuristics on the WM_CLASS X11 + * property (and some static override regexps); in the future, we want to + * have it also track through startup-notification. + * + * Second, the monitor also maintains some usage and state statistics for + * windows by keeping track of the approximate time an application's + * windows are focus, as well as the last workspace it was seen on. + * This time tracking is implemented by watching for restack notifications, + * and computing a time delta between them. Also we monitor the + * GNOME Session "StatusChanged" signal which by default is emitted after 5 + * minutes to signify idle. + */ + #define APP_MONITOR_GCONF_DIR SHELL_GCONF_DIR"/app_monitor" #define ENABLE_MONITORING_KEY APP_MONITOR_GCONF_DIR"/enable_monitoring" -/* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */ -#define DATA_FILENAME "applications_usage" +#define FOCUS_TIME_MIN_SECONDS 7 /* Need 7 continuous seconds of focus */ -/* How often we save internally app data, in seconds */ -#define SAVE_APPS_TIMEOUT 3600 /* One hour */ +#define USAGE_CLEAN_DAYS 7 /* If after 7 days we haven't seen an app, purge it */ + +#define SNAPSHOTS_PER_CLEAN_SECONDS ((USAGE_CLEAN_DAYS * 24 * 60 * 60) / MIN_SNAPSHOT_APP_SECONDS) /* One week */ -/* How often we save internally app data in burst mode */ -#define SAVE_APPS_BURST_TIMEOUT 120 /* Two minutes */ +/* Data is saved to file SHELL_CONFIG_DIR/DATA_FILENAME */ +#define DATA_FILENAME "application_state" -/* Length of the initial app burst, for each new activity */ -#define SAVE_APPS_BURST_LENGTH 3600 /* One hour */ +#define IDLE_TIME_TRANSITION_SECONDS 30 /* If we transition to idle, only count + * this many seconds of usage */ /* The ranking algorithm we use is: every time an app score reaches SCORE_MAX, * divide all scores by 2. Scores are raised by 1 unit every SAVE_APPS_TIMEOUT @@ -48,14 +67,17 @@ * To keep the list clean, and avoid being Big Brother, apps that have not been * seen for a week and whose score is below SCORE_MIN are removed. */ - -/* With this value, an app goes from bottom to top of the - * popularity list in 50 hours of use */ -#define SCORE_MAX (3600*50/SAVE_APPS_TIMEOUT) + +/* How often we save internally app data, in seconds */ +#define SAVE_APPS_TIMEOUT_SECONDS 5 /* leave this low for testing, we can bump later if need be */ + +/* With this value, an app goes from bottom to top of the + * usage list in 50 hours of use */ +#define SCORE_MAX (3600 * 50 / FOCUS_TIME_MIN_SECONDS) /* If an app's score in lower than this and the app has not been used in a week, * remove it */ -#define SCORE_MIN 5 +#define SCORE_MIN (SCORE_MAX >> 3) /* Title patterns to detect apps that don't set WM class as needed. * Format: pseudo/wanted WM class, title regex pattern, NULL (for GRegex) */ @@ -76,52 +98,47 @@ static struct }; -typedef struct AppPopularity AppPopularity; +typedef struct AppUsage AppUsage; typedef struct ActiveAppsData ActiveAppsData; struct _ShellAppMonitor { GObject parent; - GFile *configfile; - XScreenSaverInfo *info; + DBusGProxy *session_proxy; GdkDisplay *display; GConfClient *gconf_client; - glong activity_time; gulong last_idle; - guint poll_id; - guint save_apps_id; + guint idle_focus_change_id; + guint save_id; guint gconf_notify; gboolean currently_idle; gboolean enable_monitoring; - /* */ - GHashTable *running_appids; + GSList *previously_running; + + long watch_start_time; + MetaWindow *watched_window; /* */ GHashTable *window_to_app; - GHashTable *apps_by_wm_class; /* Seen apps by wm_class */ - GHashTable *popularities; /* One AppPopularity struct list per activity */ - int upload_apps_burst_count; + /* > */ + GHashTable *app_usages_for_context; }; G_DEFINE_TYPE (ShellAppMonitor, shell_app_monitor, G_TYPE_OBJECT); -/* Represents an application record for a given activity */ -struct AppPopularity +/* Represents an application record for a given context */ +struct AppUsage { - gchar *wm_class; gdouble score; /* Based on the number of times we'e seen the app and normalized */ - guint32 last_seen; /* Used to clear old apps we've only seen a few times */ -}; + long last_seen; /* Used to clear old apps we've only seen a few times */ -struct ActiveAppsData -{ - int activity; - GSList *result; - GTime start_time; + /* how many windows are currently open; in terms of persistence we only save + * whether the app had any windows or not. */ + guint window_count; }; enum { @@ -134,21 +151,11 @@ static guint signals[LAST_SIGNAL] = { 0 }; static void shell_app_monitor_finalize (GObject *object); -static void get_active_apps (ShellAppMonitor *monitor, - int in_last_seconds, - GSList **wm_classes, - int *activity); - -static void save_active_apps (ShellAppMonitor *monitor, - int collection_period, - GSList *wm_classes, - int activity); +static void on_session_status_changed (DBusGProxy *proxy, guint status, ShellAppMonitor *monitor); +static void on_focus_window_changed (MetaDisplay *display, GParamSpec *spec, ShellAppMonitor *monitor); +static void ensure_queued_save (ShellAppMonitor *monitor); -static gboolean poll_for_idleness (void *data); - -static gboolean on_save_apps_timeout (gpointer data); - -static void save_to_file (ShellAppMonitor *monitor); +static gboolean idle_save_application_usage (gpointer data); static void restore_from_file (ShellAppMonitor *monitor); @@ -159,7 +166,7 @@ static void on_enable_monitoring_key_changed (GConfClient *client, GConfEntry *entry, gpointer monitor); -static glong +static long get_time (void) { GTimeVal tv; @@ -172,7 +179,7 @@ static void shell_app_monitor_class_init(ShellAppMonitorClass *klass) GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = shell_app_monitor_finalize; - + signals[CHANGED] = g_signal_new ("changed", SHELL_TYPE_APP_MONITOR, G_SIGNAL_RUN_LAST, @@ -182,22 +189,10 @@ static void shell_app_monitor_class_init(ShellAppMonitorClass *klass) G_TYPE_NONE, 0); } -/* Little callback to destroy lists inside hash table */ static void -destroy_popularity (gpointer key, - gpointer value, - gpointer user_data) +destroy_usage (AppUsage *usage) { - GSList *list = value; - GSList *l; - AppPopularity *app_popularity; - for (l = list; l; l = l->next) - { - app_popularity = (AppPopularity *) l->data; - g_free (app_popularity->wm_class); - g_free (app_popularity); - } - g_slist_free (list); + g_free (usage); } static char * @@ -261,7 +256,8 @@ get_cleaned_wmclass_for_window (MetaWindow *window) cleaned_wmclass = g_utf8_strdown (wmclass, -1); g_free (wmclass); - /* This handles "Fedora Eclipse", probably others */ + /* This handles "Fedora Eclipse", probably others. + * Note g_strdelimit is modify-in-place. */ g_strdelimit (cleaned_wmclass, " ", '-'); wmclass = g_strdup (cleaned_wmclass); g_free (cleaned_wmclass); @@ -297,28 +293,213 @@ get_app_for_window (MetaWindow *window) return result; } +static const char * +get_window_context (MetaWindow *window) +{ + return ""; +} + +static GHashTable * +get_usages_for_context (ShellAppMonitor *monitor, + const char *context) +{ + GHashTable *context_usages; + + context_usages = g_hash_table_lookup (monitor->app_usages_for_context, context); + if (context_usages == NULL) + { + context_usages = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)destroy_usage); + g_hash_table_insert (monitor->app_usages_for_context, g_strdup (context), + context_usages); + } + return context_usages; +} + +static AppUsage * +get_app_usage_for_context_and_id (ShellAppMonitor *monitor, + const char *context, + const char *appid) +{ + AppUsage *usage; + GHashTable *context_usages; + + context_usages = get_usages_for_context (monitor, context); + + usage = g_hash_table_lookup (context_usages, appid); + if (usage) + return usage; + + usage = g_new0 (AppUsage, 1); + g_hash_table_insert (context_usages, g_strdup (appid), usage); + + return usage; +} + +static AppUsage * +get_app_usage_from_window (ShellAppMonitor *monitor, + MetaWindow *window) +{ + ShellAppInfo *app; + const char *context; + + app = g_hash_table_lookup (monitor->window_to_app, window); + if (!app) + return NULL; + + context = get_window_context (window); + + return get_app_usage_for_context_and_id (monitor, context, shell_app_info_get_id (app)); +} + +static MetaWindow * +get_active_window (ShellAppMonitor *monitor) +{ + MetaScreen *screen; + MetaDisplay *display; + screen = shell_global_get_screen (shell_global_get ()); + display = meta_screen_get_display (screen); + return meta_display_get_focus_window (display); +} + +typedef struct { + gboolean in_context; + GHashTableIter context_iter; + const char *context_id; + GHashTableIter usage_iter; +} UsageIterator; + +static void +usage_iterator_init (ShellAppMonitor *self, + UsageIterator *iter) +{ + iter->in_context = FALSE; + g_hash_table_iter_init (&(iter->context_iter), self->app_usages_for_context); +} + +static gboolean +usage_iterator_next (ShellAppMonitor *self, + UsageIterator *iter, + const char **context, + const char **id, + AppUsage **usage) +{ + gpointer key, value; + gboolean next_context; + + if (!iter->in_context) + next_context = TRUE; + else if (!g_hash_table_iter_next (&(iter->usage_iter), &key, &value)) + next_context = TRUE; + else + next_context = FALSE; + + while (next_context) + { + GHashTable *app_usages; + + if (!g_hash_table_iter_next (&(iter->context_iter), &key, &value)) + return FALSE; + iter->in_context = TRUE; + iter->context_id = key; + app_usages = value; + g_hash_table_iter_init (&(iter->usage_iter), app_usages); + + next_context = !g_hash_table_iter_next (&(iter->usage_iter), &key, &value); + } + + *context = iter->context_id; + *id = key; + *usage = value; + + return TRUE; +} + +static void +usage_iterator_remove (ShellAppMonitor *self, + UsageIterator *iter) +{ + g_assert (iter->in_context); + + g_hash_table_iter_remove (&(iter->usage_iter)); +} + +/* Limit the score to a certain level so that most used apps can change */ +static void +normalize_usage (ShellAppMonitor *self) +{ + UsageIterator iter; + const char *context; + const char *id; + AppUsage *usage; + + usage_iterator_init (self, &iter); + + while (usage_iterator_next (self, &iter, &context, &id, &usage)) + { + usage->score /= 2; + } +} + +static void +increment_usage_for_window_at_time (ShellAppMonitor *self, + MetaWindow *window, + long time) +{ + AppUsage *usage; + guint elapsed; + guint usage_count; + + usage = get_app_usage_from_window (self, window); + if (usage == NULL) + { + /* This could in theory happen if we lost the app tracking, i.e. + * the window changed WM_CLASS. In that case, time for a punt. + */ + return; + } + + usage->last_seen = time; + + elapsed = time - self->watch_start_time; + usage_count = elapsed / FOCUS_TIME_MIN_SECONDS; + if (usage_count > 0) + { + usage->score += usage_count; + if (usage->score > SCORE_MAX) + normalize_usage (self); + ensure_queued_save (self); + } +} + +static void +increment_usage_for_window (ShellAppMonitor *self, + MetaWindow *window) +{ + long curtime = get_time (); + increment_usage_for_window_at_time (self, window, curtime); +} + static void track_window (ShellAppMonitor *self, MetaWindow *window) { ShellAppInfo *app; - guint window_count; - const char *appid; + AppUsage *usage; app = get_app_for_window (window); if (!app) return; - /* Steal the ref */ g_hash_table_insert (self->window_to_app, window, app); - appid = shell_app_info_get_id (app); - - window_count = GPOINTER_TO_UINT (g_hash_table_lookup (self->running_appids, appid)); + usage = get_app_usage_from_window (self, window); - window_count += 1; - g_hash_table_insert (self->running_appids, g_strdup (appid), GUINT_TO_POINTER (window_count)); - if (window_count == 1) + /* Ephemerally keep track of the number of windows open for this app, + * when it switches between 0 and 1 we emit a changed signal. + */ + usage->window_count++; + usage->last_seen = get_time (); + if (usage->window_count == 1) g_signal_emit (self, signals[CHANGED], 0); } @@ -339,27 +520,21 @@ shell_app_monitor_on_window_removed (MetaWorkspace *workspace, { ShellAppMonitor *self = SHELL_APP_MONITOR (user_data); ShellAppInfo *app; - const char *appid; - guint window_count; + AppUsage *usage; app = g_hash_table_lookup (self->window_to_app, window); if (!app) return; - appid = shell_app_info_get_id (app); - window_count = GPOINTER_TO_UINT (g_hash_table_lookup (self->running_appids, appid)); + usage = get_app_usage_from_window (self, window); + + if (window == self->watched_window) + self->watched_window = NULL; + + usage->window_count--; + if (usage->window_count == 0) + g_signal_emit (self, signals[CHANGED], 0); - window_count -= 1; - if (window_count == 0) - { - g_hash_table_remove (self->running_appids, appid); - g_signal_emit (self, signals[CHANGED], 0); - } - else - { - g_hash_table_insert (self->running_appids, g_strdup (appid), - GUINT_TO_POINTER (window_count)); - } g_hash_table_remove (self->window_to_app, window); } @@ -377,12 +552,52 @@ load_initial_windows (ShellAppMonitor *monitor) GList *window_iter; for (window_iter = windows; window_iter; window_iter = window_iter->next) - track_window (monitor, (MetaWindow*)window_iter->data); + { + MetaWindow *window = window_iter->data; + track_window (monitor, window); + } g_list_free (windows); } } +static void +on_session_status_changed (DBusGProxy *proxy, + guint status, + ShellAppMonitor *monitor) +{ + gboolean idle; + + idle = (status >= 3); + if (monitor->currently_idle == idle) + return; + + monitor->currently_idle = idle; + if (idle) + { + long end_time; + + /* Resync the active window, it may have changed while + * we were idle and ignoring focus changes. + */ + monitor->watched_window = get_active_window (monitor); + + /* The GNOME Session signal we watch is 5 minutes, but that's a long + * time for this purpose. Instead, just add a base 30 seconds. + */ + if (monitor->watched_window) + { + end_time = monitor->watch_start_time + IDLE_TIME_TRANSITION_SECONDS; + increment_usage_for_window_at_time (monitor, monitor->watched_window, end_time); + } + } + else + { + /* Transitioning to !idle, reset the start time */ + monitor->watch_start_time = get_time (); + } +} + /** * shell_app_monitor_get_windows_for_app: * @self: @@ -449,10 +664,14 @@ shell_app_monitor_on_n_workspaces_changed (MetaScreen *screen, static void init_window_monitoring (ShellAppMonitor *self) { + MetaDisplay *display; MetaScreen *screen = shell_global_get_screen (shell_global_get ()); g_signal_connect (screen, "notify::n-workspaces", G_CALLBACK (shell_app_monitor_on_n_workspaces_changed), self); + display = meta_screen_get_display (screen); + g_signal_connect (display, "notify::focus-window", + G_CALLBACK (on_focus_window_changed), self); shell_app_monitor_on_n_workspaces_changed (screen, NULL, self); } @@ -460,50 +679,44 @@ init_window_monitoring (ShellAppMonitor *self) static void shell_app_monitor_init (ShellAppMonitor *self) { - int event_base, error_base; GdkDisplay *display; - Display *xdisplay; char *path; char *shell_config_dir; + DBusGConnection *session_bus; /* FIXME: should we create as many monitors as there are GdkScreens? */ - display = gdk_display_get_default(); - xdisplay = GDK_DISPLAY_XDISPLAY (display); - if (!XScreenSaverQueryExtension (xdisplay, &event_base, &error_base)) - { - g_warning ("Screensaver extension not found on X display, can't detect user idleness"); - } - + display = gdk_display_get_default (); + + session_bus = dbus_g_bus_get (DBUS_BUS_SESSION, NULL); + self->session_proxy = dbus_g_proxy_new_for_name (session_bus, "org.gnome.SessionManager", + "/org/gnome/SessionManager/Presence", + "org.gnome.SessionManager"); + dbus_g_proxy_add_signal (self->session_proxy, "StatusChanged", + G_TYPE_UINT, G_TYPE_INVALID, G_TYPE_INVALID); + dbus_g_proxy_connect_signal (self->session_proxy, "StatusChanged", + G_CALLBACK (on_session_status_changed), self, NULL); + self->display = g_object_ref (display); - self->info = XScreenSaverAllocInfo (); - self->activity_time = get_time (); self->last_idle = 0; self->currently_idle = FALSE; self->enable_monitoring = FALSE; - /* No need for free functions: value is an int stored as a pointer, and keys are - * freed manually in finalize () since we replace elements and reuse app names */ - self->popularities = g_hash_table_new (g_direct_hash, g_direct_equal); - self->apps_by_wm_class = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) g_free); - - self->running_appids = g_hash_table_new_full (g_str_hash, g_str_equal, - g_free, NULL); + self->app_usages_for_context = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) g_hash_table_destroy); self->window_to_app = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) shell_app_info_unref); - load_initial_windows (self); - init_window_monitoring (self); - g_object_get (shell_global_get(), "configdir", &shell_config_dir, NULL), path = g_build_filename (shell_config_dir, DATA_FILENAME, NULL); g_free (shell_config_dir); self->configfile = g_file_new_for_path (path); restore_from_file (self); + load_initial_windows (self); + init_window_monitoring (self); + self->gconf_client = gconf_client_get_default (); gconf_client_add_dir (self->gconf_client, APP_MONITOR_GCONF_DIR, GCONF_CLIENT_PRELOAD_NONE, NULL); @@ -519,15 +732,12 @@ shell_app_monitor_finalize (GObject *object) ShellAppMonitor *self = SHELL_APP_MONITOR (object); int i; - XFree (self->info); - g_source_remove (self->poll_id); - g_source_remove (self->save_apps_id); + if (self->save_id > 0) + g_source_remove (self->save_id); gconf_client_notify_remove (self->gconf_client, self->gconf_notify); g_object_unref (self->gconf_client); g_object_unref (self->display); - g_hash_table_destroy (self->apps_by_wm_class); - g_hash_table_foreach (self->popularities, destroy_popularity, NULL); - g_hash_table_destroy (self->popularities); + g_hash_table_destroy (self->app_usages_for_context); for (i = 0; title_patterns[i].app_id; i++) g_regex_unref (title_patterns[i].regex); g_object_unref (self->configfile); @@ -537,41 +747,27 @@ shell_app_monitor_finalize (GObject *object) /** * shell_app_monitor_get_most_used_apps: - * - * Get a list of desktop identifiers representing the most popular applications - * for a given activity. - * * @monitor: the app monitor instance to request - * @activity: the activity for which stats are considered + * @context: Activity identifier * @max_count: how many applications are requested. Note that the actual * list size may be less, or NULL if not enough applications are registered. * - * Returns: (element-type utf8) (transfer full): List of application desktop - * identifiers, in low case + * Get a list of desktop identifiers representing the most popular applications + * for a given context. + * + * Returns: (element-type utf8) (transfer container): List of application desktop + * identifiers */ -GSList * +GList * shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor, - gint activity, + const char *context, gint max_count) { - GSList *list = NULL; - GSList *popularity; - AppPopularity *app_popularity; - int i; - - popularity = g_hash_table_lookup (monitor->popularities, - GINT_TO_POINTER (activity)); - - for (i = 0; i < max_count; i++) - { - if (!popularity) - break; - app_popularity = (AppPopularity *) (popularity->data); - list = g_slist_prepend (list, g_utf8_strdown (app_popularity->wm_class, -1)); - popularity = popularity->next; - } - list = g_slist_reverse (list); - return list; + GHashTable *usages; + usages = g_hash_table_lookup (monitor->app_usages_for_context, context); + if (usages == NULL) + return NULL; + return g_hash_table_get_keys (usages); } /** @@ -582,7 +778,8 @@ shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor, * Returns: Desktop file id associated with window */ ShellAppInfo * -shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin) +shell_app_monitor_get_window_app (ShellAppMonitor *monitor, + MetaWindow *metawin) { ShellAppInfo *info = g_hash_table_lookup (monitor->window_to_app, metawin); if (info) @@ -592,450 +789,424 @@ shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin) /** * shell_app_monitor_get_running_app_ids: - * * @monitor: An app monitor instance + * @context: Activity identifier + * + * Returns the set of applications which currently have at least one open + * window in the given context. * * Returns: (element-type utf8) (transfer container): List of application desktop * identifiers */ -GList * -shell_app_monitor_get_running_app_ids (ShellAppMonitor *monitor) +GSList * +shell_app_monitor_get_running_app_ids (ShellAppMonitor *monitor, + const char *context) { - return g_hash_table_get_keys (monitor->running_appids); -} + UsageIterator iter; + const char *cur_context; + const char *id; + AppUsage *usage; + GSList *ret; -void -update_app_info (ShellAppMonitor *monitor) -{ - char *wm_class; - ShellGlobal *global; - GHashTable *app_active_times = NULL; /* last active time for an application */ - MetaScreen *screen; - MetaDisplay *display; - MetaWindow *active; - int activity; - guint32 timestamp; + usage_iterator_init (monitor, &iter); - global = shell_global_get (); - g_object_get (global, "screen", &screen, NULL); - display = meta_screen_get_display (screen); - g_object_unref (screen); - - active = meta_display_get_focus_window (display); - - if (active == NULL) - return; - - wm_class = get_cleaned_wmclass_for_window (active); - - if (!wm_class) - return; - - app_active_times = g_hash_table_lookup (monitor->apps_by_wm_class, wm_class); - if (!app_active_times) + ret = NULL; + while (usage_iterator_next (monitor, &iter, &cur_context, &id, &usage)) { - /* Create a hash table to save times per activity - * Value and key are int stored as pointers, no need to free them */ - app_active_times = g_hash_table_new (g_direct_hash, g_direct_equal); - g_hash_table_replace (monitor->apps_by_wm_class, g_strdup (wm_class), - app_active_times); - } + if (strcmp (cur_context, context) != 0) + continue; - timestamp = get_time (); - activity = 0; - g_hash_table_replace (app_active_times, GINT_TO_POINTER (activity), - GINT_TO_POINTER (timestamp)); + if (usage->window_count > 0) + ret = g_slist_prepend (ret, (char*)id); + } - g_free (wm_class); + return ret; } static gboolean -poll_for_idleness (gpointer data) +idle_handle_focus_change (gpointer data) { ShellAppMonitor *monitor = data; - int i; - int n_screens; - unsigned long idle_time; - gboolean was_idle; - - idle_time = G_MAXINT; - n_screens = gdk_display_get_n_screens (monitor->display); - for (i = 0; i < n_screens; ++i) - { - int result = 0; - GdkScreen *screen; - - screen = gdk_display_get_screen (monitor->display, i); - result = XScreenSaverQueryInfo (GDK_DISPLAY_XDISPLAY (monitor->display), - GDK_SCREEN_XSCREEN (screen)->root, - monitor->info); - if (result == 0) - { - g_warning ("Failed to get idle time from screensaver extension"); - break; - } - - /* monitor->info->idle is time in milliseconds since last user interaction event */ - idle_time = MIN (monitor->info->idle, idle_time); - } - - was_idle = monitor->currently_idle; - - /* If the idle time has gone down, there must have been activity since we last checked */ - if (idle_time < monitor->last_idle) - { - monitor->activity_time = get_time (); - monitor->currently_idle = FALSE; - } - else - { - /* If no activity, see how long ago it was and count ourselves idle - * if it's been a short while. We keep this idle really short, - * so it can be "more aggressive" about idle detection than - * a screensaver would be. - */ - GTime now = get_time (); - if (now < monitor->activity_time) - { - /* clock went backward... just "catch up" - * then wait until the idle timeout expires again - */ - monitor->activity_time = now; - } - else if ((now - monitor->activity_time) > 120) - { /* 120 = 2 minutes */ - monitor->currently_idle = TRUE; - } - } + long curtime = get_time (); - monitor->last_idle = idle_time; + increment_usage_for_window (monitor, monitor->watched_window); - if (!monitor->currently_idle) - { - update_app_info (monitor); - } + monitor->watched_window = get_active_window (monitor); + monitor->watch_start_time = curtime; - return TRUE; + monitor->idle_focus_change_id = 0; + return FALSE; } -/* Used to iterate over apps to create a list of those that have been active - * since the activity specified by app_data has been started */ static void -active_apps_foreach (const gpointer key, - const gpointer value, - gpointer data) +on_focus_window_changed (MetaDisplay *display, + GParamSpec *spec, + ShellAppMonitor *self) { - char *name = key; - GHashTable *app_active_times = value; /* GTime spent per activity */ - ActiveAppsData *app_data = data; - GTime active_time; - - /* Only return apps that have been used in the current activity */ - active_time = GPOINTER_TO_INT (g_hash_table_lookup - (app_active_times, GINT_TO_POINTER (app_data->activity))); - if (active_time > app_data->start_time) - app_data->result = g_slist_prepend (app_data->result, g_strdup (name)); -} - -/* - * Returns list of application names we've seen the user interacting with - * within the last 'in_last_seconds' seconds and in the specified activity. - * Free the names in the GSList with g_free(), the lists themselves with - * g_slist_free(). - */ -static void -get_active_apps (ShellAppMonitor *monitor, - int in_last_seconds, - GSList **wm_classes, - int *activity) -{ - ActiveAppsData app_data; - guint32 now; + if (!self->enable_monitoring || self->currently_idle) + return; - now = get_time (); - app_data.activity = 0; - *activity = app_data.activity; /* Be sure we use the exact same timestamp everywhere */ - app_data.start_time = now - in_last_seconds; + if (self->idle_focus_change_id != 0) + return; - if (wm_classes && g_hash_table_size (monitor->apps_by_wm_class)) - { - app_data.result = NULL; - g_hash_table_foreach (monitor->apps_by_wm_class, active_apps_foreach, - &app_data); - *wm_classes = app_data.result; - } + /* Defensively compress notifications here in case something is going berserk, + * we'll at least use a bit less system resources. */ + self->idle_focus_change_id = g_timeout_add (250, idle_handle_focus_change, self); } -static gboolean -on_save_apps_timeout (gpointer data) +static void +ensure_queued_save (ShellAppMonitor *monitor) { - ShellAppMonitor *monitor = (ShellAppMonitor *) data; - static guint32 period = SAVE_APPS_TIMEOUT; - - if (monitor->upload_apps_burst_count >= 0) - { - period = SAVE_APPS_BURST_TIMEOUT; - monitor->upload_apps_burst_count--; - - if (monitor->upload_apps_burst_count == 0) - { - g_source_remove (monitor->save_apps_id); - g_timeout_add_seconds (SAVE_APPS_TIMEOUT, on_save_apps_timeout, monitor); - } - } - - GSList *wm_classes = NULL; - int activity; - - get_active_apps (monitor, period, &wm_classes, &activity); - - if (wm_classes) - { - save_active_apps (monitor, period, wm_classes, activity); - save_to_file (monitor); - - if (wm_classes) - { - g_slist_foreach (wm_classes, (GFunc) g_free, NULL); - g_slist_free (wm_classes); - } - } - - return TRUE; -} - -/* Used to find an app item from its wm_class, when non empty */ -static gint -popularity_find_app (gconstpointer list_data, - gconstpointer user_data) -{ - AppPopularity *list_pop = (AppPopularity *) list_data; - AppPopularity *user_pop = (AppPopularity *) user_data; - return strcmp (list_pop->wm_class, user_pop->wm_class); + if (monitor->save_id != 0) + return; + monitor->save_id = g_timeout_add_seconds (SAVE_APPS_TIMEOUT_SECONDS, idle_save_application_usage, monitor); } /* Used to sort highest scores at the top */ -static gint popularity_sort_apps (gconstpointer data1, - gconstpointer data2) +static gint +usage_sort_apps (gconstpointer data1, + gconstpointer data2) { - const AppPopularity *pop1 = data1; - const AppPopularity *pop2 = data2; - - if (pop1->score > pop2->score) + const AppUsage *u1 = data1; + const AppUsage *u2 = data2; + + if (u1->score > u2->score) return -1; - else if (pop1->score == pop2->score) + else if (u1->score == u2->score) return 0; else return 1; } -/* Limit the score to a certain level so that most popular apps can change */ -static void -normalize_popularity (GSList *list) -{ - if (!list) - return; - - AppPopularity *app_popularity; - - /* Highest score since list is sorted */ - app_popularity = (AppPopularity *) (list->data); - /* Limiting score allows new apps to catch up (see SCORE_MAX definition) */ - if (app_popularity->score > SCORE_MAX) - while (list) - { - app_popularity = (AppPopularity *) (list->data); - app_popularity->score /= 2; - list = list->next; - } -} - /* Clean up apps we see rarely. * The logic behind this is that if an app was seen less than SCORE_MIN times * and not seen for a week, it can probably be forgotten about. * This should much reduce the size of the list and avoid 'pollution'. */ -static GSList * -clean_popularity (GSList *list) +static gboolean +idle_clean_usage (ShellAppMonitor *monitor) { - AppPopularity *app_popularity; + UsageIterator iter; + const char *context; + const char *id; + AppUsage *usage; GDate *date; guint32 date_days; - GSList *next, *head; + /* Subtract a week */ date = g_date_new (); g_date_set_time_t (date, time (NULL)); - g_date_subtract_days (date, 7); + g_date_subtract_days (date, USAGE_CLEAN_DAYS); date_days = g_date_get_julian (date); - head = list; - while (list) - { - next = list->next; - app_popularity = (AppPopularity *) (list->data); - if ((app_popularity->score < SCORE_MIN) && - (app_popularity->last_seen < date_days)) - head = g_slist_remove (head, list); - list = next; - } + + usage_iterator_init (monitor, &iter); + + while (usage_iterator_next (monitor, &iter, &context, &id, &usage)) + { + if ((usage->score < SCORE_MIN) && + (usage->last_seen < date_days)) + usage_iterator_remove (monitor, &iter); + } g_date_free (date); - return head; + + return FALSE; } -/* Save apps data internally to lists, merging if necessary */ -static void -save_active_apps (ShellAppMonitor *monitor, - int collection_period, - GSList *wm_classes, - int activity) +static gboolean +write_escaped (GDataOutputStream *stream, + const char *str, + GError **error) { - AppPopularity *app_popularity; - AppPopularity temp; /* We only set/use two fields here */ - GDate *date; - guint32 date_days; - GSList *popularity; - GSList *item; - GSList *l; - - popularity = g_hash_table_lookup (monitor->popularities, - GINT_TO_POINTER (activity)); - date = g_date_new (); - g_date_set_time_t (date, time (NULL)); - date_days = g_date_get_julian (date); - if (!popularity) /* Just create the list using provided information */ - { - for (l = wm_classes; l; l = l->next) - { - app_popularity = g_new (AppPopularity, 1); - app_popularity->last_seen = date_days; - app_popularity->score = 1; - /* Copy data from the old list */ - app_popularity->wm_class = g_strdup ((gchar *) l->data); - popularity = g_slist_prepend (popularity, app_popularity); - } - } - else /* Merge with old data */ - { - for (l = wm_classes; l; l = l->next) - { - temp.wm_class = (gchar *) l->data; - - item = g_slist_find_custom (popularity, &temp, popularity_find_app); - if (!item) - { - app_popularity = g_new (AppPopularity, 1); - app_popularity->score = 1; - /* Copy data from other lists */ - app_popularity->wm_class = g_strdup ((gchar *) l->data); - popularity = g_slist_prepend (popularity, app_popularity); - } - else - { - app_popularity = (AppPopularity *) item->data; - app_popularity->score++; - } - app_popularity->last_seen = date_days; - } - } + gboolean ret; + char *quoted = g_markup_escape_text (str, -1); + ret = g_data_output_stream_put_string (stream, quoted, NULL, error); + g_free (quoted); + return ret; +} - /* Clean once in a while, doing so at start may no be enough if uptime is high */ - popularity = clean_popularity (popularity); - /* Need to do this often since SCORE_MAX should be relatively low */ - normalize_popularity (popularity); - popularity = g_slist_sort (popularity, popularity_sort_apps); - g_hash_table_replace (monitor->popularities, GINT_TO_POINTER (activity), - popularity); - g_date_free (date); +static gboolean +write_attribute_string (GDataOutputStream *stream, + const char *elt_name, + const char *str, + GError **error) +{ + gboolean ret = FALSE; + char *elt; + + elt = g_strdup_printf (" %s=\"", elt_name); + ret = g_data_output_stream_put_string (stream, elt, NULL, error); + g_free (elt); + if (!ret) goto out; + + ret = write_escaped (stream, str, error); + if (!ret) goto out; + + ret = g_data_output_stream_put_string (stream, "\"", NULL, error); + +out: + return ret; +} + +static gboolean +write_attribute_uint (GDataOutputStream *stream, + const char *elt_name, + guint value, + GError **error) +{ + gboolean ret; + char *buf; + + buf = g_strdup_printf ("%u", value); + ret = write_attribute_string (stream, elt_name, buf, error); + g_free (buf); - g_signal_emit (monitor, signals[CHANGED], 0); + return ret; +} + +static gboolean +write_attribute_double (GDataOutputStream *stream, + const char *elt_name, + double value, + GError **error) +{ + gchar buf[G_ASCII_DTOSTR_BUF_SIZE]; + gboolean ret; + + g_ascii_dtostr (buf, sizeof (buf), value); + ret = write_attribute_string (stream, elt_name, buf, error); + + return ret; } /* Save app data lists to file */ -static void -save_to_file (ShellAppMonitor *monitor) +static gboolean +idle_save_application_usage (gpointer data) { - GHashTableIter iter; - gpointer key; - gpointer value; - int activity; - GSList *popularity; - AppPopularity *app_popularity; + ShellAppMonitor *monitor = SHELL_APP_MONITOR (data); + UsageIterator iter; + const char *current_context; + const char *context; + const char *id; + AppUsage *usage; GFileOutputStream *output; + GOutputStream *buffered_output; GDataOutputStream *data_output; GError *error = NULL; - gchar *line; - gchar score_buf[G_ASCII_DTOSTR_BUF_SIZE]; - static int last_error_code = 0; + + + monitor->save_id = 0; /* Parent directory is already created by shell-global */ output = g_file_replace (monitor->configfile, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error); if (!output) { - if (last_error_code == error->code) - { - g_error_free (error); - return; - } - last_error_code = error->code; - g_warning ("Could not save applications usage data: %s. This warning will be printed only once.", error->message); + g_debug ("Could not save applications usage data: %s", error->message); g_error_free (error); - return; + return FALSE; } - data_output = g_data_output_stream_new (G_OUTPUT_STREAM(output)); + buffered_output = g_buffered_output_stream_new (G_OUTPUT_STREAM(output)); g_object_unref (output); - - g_hash_table_iter_init (&iter, monitor->popularities); - while (g_hash_table_iter_next (&iter, &key, &value) && value) - { - activity = GPOINTER_TO_INT (key); - popularity = value; - - line = g_strdup_printf ("%i\n", activity); - g_data_output_stream_put_string (data_output, "--\n", NULL, NULL); - g_data_output_stream_put_string (data_output, line, NULL, NULL); - g_free (line); - - do + data_output = g_data_output_stream_new (G_OUTPUT_STREAM(buffered_output)); + g_object_unref (buffered_output); + + if (!g_data_output_stream_put_string (data_output, "\n\n", NULL, &error)) + goto out; + + usage_iterator_init (monitor, &iter); + + current_context = NULL; + while (usage_iterator_next (monitor, &iter, &context, &id, &usage)) + { + if (context != current_context) { - app_popularity = (AppPopularity *) popularity->data; - g_ascii_dtostr (score_buf, sizeof (score_buf), app_popularity->score); - line = g_strdup_printf ("%s,%s,%u\n", app_popularity->wm_class, - score_buf, app_popularity->last_seen); - g_data_output_stream_put_string (data_output, line, NULL, &error); - g_free (line); - if (error) + if (current_context != NULL) + { + if (!g_data_output_stream_put_string (data_output, " ", NULL, &error)) + goto out; + } + current_context = context; + if (!g_data_output_stream_put_string (data_output, " \n", NULL, &error)) goto out; } - while ( (popularity = popularity->next) ); + if (!g_data_output_stream_put_string (data_output, " window_count > 0, &error)) + goto out; + + if (!write_attribute_double (data_output, "score", usage->score, &error)) + goto out; + if (!write_attribute_uint (data_output, "last-seen", usage->last_seen, &error)) + goto out; + if (!g_data_output_stream_put_string (data_output, "/>\n", NULL, &error)) + goto out; + } + if (current_context != NULL) + { + if (!g_data_output_stream_put_string (data_output, " \n", NULL, &error)) + goto out; } + if (!g_data_output_stream_put_string (data_output, "\n", NULL, &error)) + goto out; - out: - g_output_stream_close (G_OUTPUT_STREAM(data_output), NULL, &error); +out: + if (!error) + g_output_stream_close (G_OUTPUT_STREAM(data_output), NULL, &error); g_object_unref (data_output); if (error) { - if (last_error_code == error->code) + g_debug ("Could not save applications usage data: %s", error->message); + g_error_free (error); + } + return FALSE; +} + +typedef struct { + ShellAppMonitor *monitor; + char *context; +} ParseData; + +static void +shell_app_monitor_start_element_handler (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseData *data = user_data; + + if (strcmp (element_name, "application-state") == 0) + { + } + else if (strcmp (element_name, "context") == 0) + { + char *context = NULL; + const char **attribute; + const char **value; + + for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++) + { + if (strcmp (*attribute, "id") == 0) + context = g_strdup (*value); + } + if (context < 0) { - g_error_free (error); + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + "Missing attribute id on <%s> element", + element_name); return; } - last_error_code = error->code; - g_warning ("Could not save applications usage data: %s. This warning will be printed only once.", error->message); - g_error_free (error); + data->context = context; + } + else if (strcmp (element_name, "application") == 0) + { + const char **attribute; + const char **value; + AppUsage *usage; + char *appid = NULL; + GHashTable *usage_table; + + for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++) + { + if (strcmp (*attribute, "id") == 0) + appid = g_strdup (*value); + } + + if (!appid) + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + "Missing attribute id on <%s> element", + element_name); + return; + } + + usage_table = get_usages_for_context (data->monitor, data->context); + + usage = g_new0 (AppUsage, 1); + g_hash_table_insert (usage_table, appid, usage); + + for (attribute = attribute_names, value = attribute_values; *attribute; attribute++, value++) + { + if (strcmp (*attribute, "open-window-count") == 0) + { + guint count = strtoul (*value, NULL, 10); + if (count > 0) + data->monitor->previously_running = g_slist_prepend (data->monitor->previously_running, + usage); + } + else if (strcmp (*attribute, "score") == 0) + { + usage->score = g_ascii_strtod (*value, NULL); + } + else if (strcmp (*attribute, "last-seen") == 0) + { + usage->last_seen = (guint) g_ascii_strtoull (*value, NULL, 10); + } + } + } + else + { + g_set_error (error, + G_MARKUP_ERROR, + G_MARKUP_ERROR_PARSE, + "Unknown element <%s>", + element_name); } } +static void +shell_app_monitor_end_element_handler (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseData *data = user_data; + + if (strcmp (element_name, "context") == 0) + { + g_free (data->context); + data->context = NULL; + } +} + +static void +shell_app_monitor_text_handler (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + /* do nothing, very very fast */ +} + +static GMarkupParser app_state_parse_funcs = +{ + shell_app_monitor_start_element_handler, + shell_app_monitor_end_element_handler, + shell_app_monitor_text_handler, + NULL, + NULL +}; + /* Load data about apps usage from file */ static void restore_from_file (ShellAppMonitor *monitor) { - int activity = -1; /* Means invalid ID */ - GSList *popularity = NULL; - AppPopularity *app_popularity; GFileInputStream *input; - GDataInputStream *data_input; + ParseData parse_data; + GMarkupParseContext *parse_context; GError *error = NULL; - gchar *line; - gchar **info; - + char buf[1024]; + input = g_file_read (monitor->configfile, NULL, &error); if (error) { @@ -1046,75 +1217,34 @@ restore_from_file (ShellAppMonitor *monitor) return; } - data_input = g_data_input_stream_new (G_INPUT_STREAM(input)); - g_object_unref (input); + memset (&parse_data, 0, sizeof (ParseData)); + parse_data.monitor = monitor; + parse_data.context = NULL; + parse_context = g_markup_parse_context_new (&app_state_parse_funcs, 0, &parse_data, NULL); while (TRUE) { - line = g_data_input_stream_read_line (data_input, NULL, NULL, &error); - if (!line) + gssize count = g_input_stream_read ((GInputStream*) input, buf, sizeof(buf), NULL, &error); + if (count <= 0) + goto out; + if (!g_markup_parse_context_parse (parse_context, buf, count, &error)) goto out; - if (strcmp (line, "--") == 0) /* Line starts a new activity */ - { - g_free (line); - line = g_data_input_stream_read_line (data_input, NULL, NULL, &error); - if (line && (strcmp (line, "") != 0)) - { - if (activity != -1) /* Save previous activity, cleaning and sorting it */ - { - popularity = clean_popularity (popularity); - popularity = g_slist_sort (popularity, popularity_sort_apps); - g_hash_table_replace (monitor->popularities, - GINT_TO_POINTER (activity), popularity); - popularity = NULL; - } - activity = atoi (line); - /* FIXME: do something if conversion fails! */ - /* like: errno = NULL; ... if (errno) { g_free (line); goto out; } */ - } - else - { - g_free (line); - goto out; - } - } - /* Line is about an app. - * If no activity was provided yet, just skip */ - else if ((activity != -1) && (strcmp (line, "") != 0)) - { - info = g_strsplit (line, ",", 0); - if (info[0] && info [1] && info[2]) /* Skip on wrong syntax */ - { - app_popularity = g_new (AppPopularity, 1); - app_popularity->wm_class = g_strdup(info[0]); - app_popularity->score = g_ascii_strtod (info[1], NULL); - app_popularity->last_seen = (guint32) strtoul (info[2], NULL, 10); - popularity = g_slist_prepend (popularity, app_popularity); - } - - g_strfreev (info); - g_free (line); - } - else - g_free (line); /* Just skip to next app */ } out: - if (activity != -1) /* Save last activity, cleaning and sorting it */ + g_free (parse_data.context); + g_markup_parse_context_free (parse_context); + g_input_stream_close ((GInputStream*)input, NULL, NULL); + g_object_unref (input); + + idle_clean_usage (monitor); + monitor->previously_running = g_slist_sort (monitor->previously_running, usage_sort_apps); + + if (error) { - popularity = clean_popularity (popularity); - popularity = g_slist_sort (popularity, popularity_sort_apps); - g_hash_table_replace (monitor->popularities, GINT_TO_POINTER (activity), - popularity); + g_warning ("Could not load applications usage data: %s", error->message); + g_error_free (error); } - - g_input_stream_close (G_INPUT_STREAM (data_input), NULL, NULL); - g_object_unref (data_input); - if (error) - { - g_warning ("Could not load applications usage data: %s", error->message); - g_error_free (error); - } } /* Enable or disable the timers, depending on the value of ENABLE_MONITORING_KEY @@ -1135,29 +1265,18 @@ update_enable_monitoring (ShellAppMonitor *monitor) } else /* Schema is not present, set default value by hand to avoid getting FALSE */ enable = TRUE; - + /* Be sure not to start the timers if they were already set */ if (enable && !monitor->enable_monitoring) { - /* If no stats are available so far, set burst mode on */ - if (g_hash_table_size (monitor->popularities)) - monitor->upload_apps_burst_count = 0; - else - monitor->upload_apps_burst_count = SAVE_APPS_BURST_LENGTH / SAVE_APPS_BURST_TIMEOUT; - - monitor->poll_id = g_timeout_add_seconds (5, poll_for_idleness, monitor); - if (monitor->upload_apps_burst_count > 0) - monitor->save_apps_id = - g_timeout_add_seconds (SAVE_APPS_BURST_TIMEOUT, on_save_apps_timeout, monitor); - else - monitor->save_apps_id = - g_timeout_add_seconds (SAVE_APPS_TIMEOUT, on_save_apps_timeout, monitor); + on_focus_window_changed (NULL, NULL, monitor); } /* ...and don't try to stop them if they were not running */ else if (!enable && monitor->enable_monitoring) { - g_source_remove (monitor->poll_id); - g_source_remove (monitor->save_apps_id); + monitor->watched_window = NULL; + if (monitor->save_id) + g_source_remove (monitor->save_id); } monitor->enable_monitoring = enable; diff --git a/src/shell-app-monitor.h b/src/shell-app-monitor.h index 1470455f8..41f712971 100644 --- a/src/shell-app-monitor.h +++ b/src/shell-app-monitor.h @@ -40,15 +40,14 @@ ShellAppMonitor* shell_app_monitor_get_default(void); ShellAppInfo *shell_app_monitor_get_window_app (ShellAppMonitor *monitor, MetaWindow *metawin); -/* Get the most popular applications for a given activity */ -GSList *shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor, - int activity, - gint number); +GList *shell_app_monitor_get_most_used_apps (ShellAppMonitor *monitor, + const char *context, + gint number); GSList *shell_app_monitor_get_windows_for_app (ShellAppMonitor *monitor, const char *appid); /* Get whatever's running right now */ -GList *shell_app_monitor_get_running_app_ids (ShellAppMonitor *monitor); +GSList *shell_app_monitor_get_running_app_ids (ShellAppMonitor *monitor, const char *context); G_END_DECLS